package io.sundial.util;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarFile;

/**
 * 资源工具类
 *
 * @author Payne 646742615@qq.com
 * 2018/12/10 13:28
 */
public class ResKit {

    public static File mkdir(String dir) {
        return mkdir(new File(dir));
    }

    public static File mkdir(File dir) {
        if (!dir.exists() && !dir.mkdir() && !dir.exists()) {
            throw new IllegalStateException("could not make directory: " + dir);
        }
        return dir;
    }

    public static File mkdirs(String dir) {
        return mkdirs(new File(dir));
    }

    public static File mkdirs(File dir) {
        if (!dir.exists() && !dir.mkdirs() && !dir.exists()) {
            throw new IllegalStateException("could not make directory: " + dir);
        }
        return dir;
    }

    public static URL toURL(File file) {
        try {
            return file.toURI().toURL();
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException(e);
        }
    }

    public static void close(Closeable closeable) {
        close(closeable, true);
    }

    public static void close(Closeable closeable, boolean quietly) throws RuntimeException {
        if (closeable == null) {
            return;
        }
        try {
            closeable.close();
        } catch (IOException e) {
            if (quietly) {
                return;
            }
            throw new RuntimeException(e);
        }
    }

    public static void close(URLClassLoader urlClassLoader) {
        try {
            Field ucp = URLClassLoader.class.getDeclaredField("ucp");
            ucp.setAccessible(true);
            Object urlClassPath = ucp.get(urlClassLoader);
            URL[] list = urlClassLoader.getURLs();
            for (int i = 0; i < list.length; i++) {
                Method getLoader = urlClassPath.getClass().getDeclaredMethod("getLoader", int.class);
                getLoader.setAccessible(true);
                Object jarLoader = getLoader.invoke(urlClassPath, i);
                Method ensureOpen = jarLoader.getClass().getDeclaredMethod("ensureOpen");
                ensureOpen.setAccessible(true);
                ensureOpen.invoke(jarLoader);
                Method getJarFile = jarLoader.getClass().getDeclaredMethod("getJarFile");
                getJarFile.setAccessible(true);
                JarFile jar = (JarFile) getJarFile.invoke(jarLoader);
                jar.close();
            }
        } catch (Exception ignored) {
            ignored.printStackTrace();
            /* ignored */
        } finally {
            close((Closeable) urlClassLoader);
        }
    }

    public static String readln(InputStream in) throws IOException {
        return readln(in, null);
    }

    public static String readln(InputStream in, String charset) throws IOException {
        int b = in.read();
        if (b == -1) {
            return null;
        }
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        while (b != -1) {
            switch (b) {
                case '\r':
                    break;
                case '\n':
                    return baos.toString();
                default:
                    baos.write(b);
                    break;
            }
            b = in.read();
        }
        return charset != null ? baos.toString(charset) : baos.toString();
    }

    public static String readln(Reader reader) throws Exception {
        return readln(reader, null);
    }

    public static String readln(Reader reader, String charset) throws Exception {
        int b = reader.read();
        if (b == -1) {
            return null;
        }
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        while (b != -1) {
            switch (b) {
                case '\r':
                    break;
                case '\n':
                    return baos.toString();
                default:
                    baos.write(b);
                    break;
            }
            b = reader.read();
        }
        return charset != null ? baos.toString(charset) : baos.toString();
    }

    public static void writeln(String line, OutputStream out) throws IOException {
        if (line == null) {
            return;
        }
        ByteArrayInputStream bais = new ByteArrayInputStream(line.getBytes());
        int b;
        while ((b = bais.read()) != -1) {
            out.write(b);
        }
        out.write('\r');
        out.write('\n');
    }

    public static void writeln(String line, Writer writer) throws IOException {
        if (line == null) {
            return;
        }
        ByteArrayInputStream bais = new ByteArrayInputStream(line.getBytes());
        int b;
        while ((b = bais.read()) != -1) {
            writer.write(b);
        }
        writer.write('\r');
        writer.write('\n');
    }

    public static long transfer(InputStream in, int length, OutputStream out) throws IOException {
        int total = 0;
        byte[] buffer = new byte[4096];
        int len;
        while ((len = in.read(buffer, 0, Math.min(length - total, buffer.length))) != -1) {
            out.write(buffer, 0, len);
            total += len;
            if (total == length) {
                break;
            }
        }
        return total;
    }

    public static long transfer(InputStream in, OutputStream out) throws IOException {
        long total = 0;
        byte[] buffer = new byte[4096];
        int length;
        while ((length = in.read(buffer)) != -1) {
            out.write(buffer, 0, length);
            total += length;
        }
        return total;
    }

    public static long transfer(Reader reader, Writer writer) throws IOException {
        long total = 0;
        char[] buffer = new char[4096];
        int length;
        while ((length = reader.read(buffer)) != -1) {
            writer.write(buffer, 0, length);
            total += length;
        }
        return total;
    }

    public static long transfer(InputStream in, File target) throws IOException {
        FileOutputStream out = null;
        try {
            out = new FileOutputStream(target);
            return transfer(in, out);
        } finally {
            close(out);
        }
    }

    public static long transfer(Reader reader, File target) throws IOException {
        FileOutputStream out = null;
        OutputStreamWriter writer = null;
        try {
            out = new FileOutputStream(target);
            writer = new OutputStreamWriter(out);
            return transfer(reader, writer);
        } finally {
            close(writer);
            close(out);
        }
    }

    public static long transfer(File source, OutputStream out) throws IOException {
        FileInputStream in = null;
        try {
            in = new FileInputStream(source);
            return transfer(in, out);
        } finally {
            close(in);
        }
    }

    public static long transfer(File source, Writer writer) throws IOException {
        FileInputStream in = null;
        InputStreamReader reader = null;
        try {
            in = new FileInputStream(source);
            reader = new InputStreamReader(in);
            return transfer(reader, writer);
        } finally {
            close(reader);
            close(in);
        }
    }

    public static long transfer(File source, File target) throws IOException {
        FileInputStream in = null;
        FileOutputStream out = null;
        try {
            in = new FileInputStream(source);
            out = new FileOutputStream(target);
            return transfer(in, out);
        } finally {
            close(in);
            close(out);
        }
    }

    public static long transfer(InputStream in, Writer writer) throws IOException {
        Reader reader = null;
        try {
            return transfer(reader = new InputStreamReader(in), writer);
        } finally {
            close(reader);
        }
    }

    public static long transfer(Reader reader, OutputStream out) throws IOException {
        Writer writer = null;
        try {
            long total = transfer(reader, writer = new OutputStreamWriter(out));
            writer.flush();
            return total;
        } finally {
            close(writer);
        }
    }

    public static InputStream transfer(InputStream in) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        transfer(in, out);
        return new ByteArrayInputStream(out.toByteArray());
    }

    public static Reader transfer(Reader reader) throws IOException {
        StringWriter writer = new StringWriter();
        transfer(reader, writer);
        return new StringReader(writer.toString());
    }

    public static String toString(InputStream in) throws IOException {
        return toString(in, null);
    }

    public static String toString(Reader reader) throws IOException {
        return toString(reader, null);
    }

    public static String toString(InputStream in, String charset) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        transfer(in, baos);
        return charset != null ? baos.toString(charset) : baos.toString();
    }

    public static String toString(Reader reader, String charset) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        transfer(reader, baos);
        return charset != null ? baos.toString(charset) : baos.toString();
    }

    public static String toString(File file) throws IOException {
        return toString(file, null);
    }

    public static String toString(File file, String charset) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        transfer(file, baos);
        return charset != null ? baos.toString(charset) : baos.toString();
    }

    public static boolean delete(File file) {
        return delete(file, false);
    }

    public static boolean delete(File file, boolean recursively) {
        if (file.isDirectory() && recursively) {
            boolean deleted = true;
            File[] files = file.listFiles();
            for (int i = 0; files != null && i < files.length; i++) {
                deleted &= delete(files[i], true);
            }
            return deleted && file.delete();
        } else {
            return file.delete();
        }
    }

    public static boolean isRelative(String path) {
        return !isAbsolute(path);
    }

    public static boolean isAbsolute(String path) {
        if (path.startsWith("/")) {
            return true;
        }
        Set<File> roots = new HashSet<>();
        Collections.addAll(roots, File.listRoots());
        File root = new File(path);
        while (root.getParentFile() != null) {
            root = root.getParentFile();
        }
        return roots.contains(root);
    }

    public static String absolutize(String path) {
        return normalize(isAbsolute(path) ? path : System.getProperty("sundial.home", System.getProperty("user.dir")) + File.separator + path);
    }

    public static String normalize(String path) {
        return path.replaceAll("[/\\\\]+", "/");
    }
}
